package com.ysw.spring.spring_util.redisson;

import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import java.util.concurrent.TimeUnit;

/**
 * 基于 Redisson 的锁的工具类
 * <p>
 * Created by zhangqingpo on 2020/08/13.
 */
@Data
//@Component
@Slf4j
public class RedissonLockUtil {

    /**
     * redis前缀，用于区分不同的应用
     **/
    @Value("${redis.common.prefix:}")
    public String redisPrefix;

  //  @Autowired(required = false)
    //@Qualifier("lockRedissonClient")
    private RedissonClient redissonClient;

    /**
     * 分布式锁实现
     *
     * @param lockName   锁名称
     * @param businessId 业务ID
     * @param handle     业务处理
     */
    @Transactional(rollbackFor = Exception.class)
    public void lock(String lockName, Object businessId, VoidHandle handle) {
        RLock rLock = getLock(lockName, businessId);
        try {
            rLock.lock();
            log.info("业务ID{}，获取锁成功", businessId);
            handle.execute();
        } finally {
            rLock.unlock();
        }
    }

    /**
     * 带返回值分布式锁实现
     *
     * @param lockName   锁名称
     * @param businessId 业务ID
     * @param handle     业务处理
     * @param <T>        返回值
     * @return
     */
    @Transactional(rollbackFor = Exception.class)
    public <T> T lock(String lockName, Object businessId, ReturnHandle<T> handle) {
        RLock rLock = getLock(lockName, businessId);
        try {
            rLock.lock();
            log.info("业务ID{}，获取锁成功", businessId);
            return handle.execute();
        } finally {
            rLock.unlock();
        }
    }

    /**
     * 分布式锁实现，等待
     *
     * @param lockName   锁名称
     * @param businessId 业务ID
     * @param handle     业务处理
     */
    @Transactional(rollbackFor = Exception.class)
    public void tryLock(String lockName, Object businessId, VoidHandle handle) {
        RLock rLock = getLock(lockName, businessId);
        if (!rLock.tryLock()) {
            log.info("业务ID{}，获取锁失败，返回", businessId);
            return;
        }

        try {
            log.info("业务ID{}，获取锁成功", businessId);
            handle.execute();
        } finally {
            rLock.unlock();
        }
    }

    /**
     * 带返回值分布式锁实现，等待
     *
     * @param lockName   锁名称
     * @param businessId 业务ID
     * @param handle     业务处理
     * @param <T>        返回值
     * @return
     */
    @Transactional(rollbackFor = Exception.class)
    public <T> T tryLock(String lockName, Object businessId, ReturnHandle<T> handle) {
        RLock rLock = getLock(lockName, businessId);
        if (!rLock.tryLock()) {
            log.info("业务ID{}，获取锁失败，返回null", businessId);
            return null;
        }

        try {
            log.info("业务ID{}，获取锁成功", businessId);
            return handle.execute();
        } finally {
            rLock.unlock();
        }
    }

    /**
     * 分布式锁实现，未抢到抛异常
     *
     * @param lockName   锁名称
     * @param businessId 业务ID
     * @param handle     业务处理
     */
    @Transactional(rollbackFor = Exception.class)
    public void tryLockException(String lockName, Object businessId, VoidHandle handle) {
        RLock rLock = getLock(lockName, businessId);
        if (!rLock.tryLock()) {
            log.info("业务ID{}，获取锁失败，抛异常处理", businessId);
            throw new RuntimeException("处理中");
        }

        try {
            log.info("业务ID{}，获取锁成功", businessId);
            handle.execute();
        } finally {
            rLock.unlock();
        }
    }

    /**
     * 带返回值分布式锁实现，未抢到抛异常
     *
     * @param lockName   锁名称
     * @param businessId 业务ID
     * @param handle     业务处理
     * @param <T>        返回值
     * @return
     */
    @Transactional(rollbackFor = Exception.class)
    public <T> T tryLockException(String lockName, Object businessId, ReturnHandle<T> handle) {
        RLock rLock = getLock(lockName, businessId);
        if (!rLock.tryLock()) {
            log.info("业务ID{}，获取锁失败，抛异常处理", businessId);
            throw new RuntimeException("处理中");
        }

        try {
            log.info("业务ID{}，获取锁成功", businessId);
            return handle.execute();
        } finally {
            rLock.unlock();
        }
    }

    /**
     * 获取锁
     *
     * @param lockName
     * @param businessId
     * @return
     */
    private RLock getLock(String lockName, Object businessId) {
        log.debug("获取分布式锁lockName:{},businessId:{}", lockName, businessId);
        if (StringUtils.isEmpty(lockName)) {
            throw new RuntimeException("分布式锁KEY为空");
        }
        if (StringUtils.isEmpty(businessId)) {
            throw new RuntimeException("业务ID为空");
        }

        String lockKey = getRedisKey(lockName) + businessId.toString();
        return redissonClient.getLock(lockKey);
    }

    public void lock(String lockKey) {
        RLock lock = redissonClient.getLock(getRedisKey(lockKey));
        lock.lock();
    }

    public void unlock(String lockKey) {
        RLock lock = redissonClient.getLock(getRedisKey(lockKey));
        lock.unlock();
    }

    public void lock(String lockKey, int leaseTime) {
        RLock lock = redissonClient.getLock(getRedisKey(lockKey));
        lock.lock(leaseTime, TimeUnit.SECONDS);
    }

    public void lock(String lockKey, TimeUnit unit, int timeout) {
        RLock lock = redissonClient.getLock(getRedisKey(lockKey));
        lock.lock(timeout, unit);
    }

    public boolean tryLock(String lockKey) {
        RLock lock = redissonClient.getLock(getRedisKey(lockKey));
        return lock.tryLock();
    }

    public boolean tryLock(String lockKey, long waitTime, long leaseTime,
                           TimeUnit unit) throws InterruptedException {
        RLock lock = redissonClient.getLock(getRedisKey(lockKey));
        return lock.tryLock(waitTime, leaseTime, unit);
    }

    public boolean isLocked(String lockKey) {
        RLock lock = redissonClient.getLock(getRedisKey(lockKey));
        return lock.isLocked();
    }

    /**
     * 判断是否为当先线程持有锁
     *
     * @param lockKey
     * @return
     */
    public boolean isHeldByCurrentThread(String lockKey) {
        RLock lock = redissonClient.getLock(getRedisKey(lockKey));
        return lock.isHeldByCurrentThread();
    }


    public interface VoidHandle {
        /**
         * 业务处理
         */
        void execute();
    }

    public interface ReturnHandle<T> {
        /**
         * 业务处理
         *
         * @return
         */
        T execute();
    }

    /**
     * 构建key
     */
    private String getRedisKey(String key) {
        return redisPrefix + ":" + key;
    }
}
